package com.clei.netty.sync.client;

import com.clei.consts.NettyConstants;
import com.clei.netty.file.enums.FileCmdTypeEnum;
import com.clei.netty.file.enums.FileSyncCmdTypeEnum;
import com.clei.netty.sync.dto.ClientParam;
import com.clei.netty.sync.dto.MsgBody;
import com.clei.utils.PrintUtil;
import com.clei.utils.helper.BytesReader;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.io.File;
import java.io.RandomAccessFile;

/**
 * file sync client handler
 *
 * @author Y
 * @date 2024-08-14
 */
public class FileSyncClientHandler extends SimpleChannelInboundHandler<MsgBody> {

    /**
     * 客户端参数
     */
    private final ClientParam clientParam;

    public FileSyncClientHandler(ClientParam clientParam) {
        this.clientParam = clientParam;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MsgBody msg) {
        byte cmd = msg.getCmd();
        FileSyncCmdTypeEnum cmdType = FileSyncCmdTypeEnum.getByCmdCode(cmd);
        try {
            switch (cmdType) {
                case REQ_FILE_DIRECTORY:
                    handleFileReq(ctx, msg, false);
                    break;
                case REQ_FILE_DATA:
                    handleFileReq(ctx, msg, true);
                    break;
                case REQ_FINISH:
                    PrintUtil.log("文件同步结束");
                    this.clientParam.getBossGroup().shutdownGracefully();
                    break;
                case RES_FILE_NOT_EXISTS:
                    PrintUtil.log("文件不存在");
                    this.clientParam.getBossGroup().shutdownGracefully();
                    break;
                default:
            }
        } catch (Exception e) {
            PrintUtil.log("请求处理出错 msg : {}", msg, e);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        PrintUtil.log("[{}]异常关闭", ctx.channel().remoteAddress(), cause);
        // 遇到异常就关闭连接
        ctx.close();
    }

    /**
     * handleFileReq
     *
     * @param ctx    ctx 频道上下文
     * @param msg    MsgBody
     * @param isFile true文件 false目录
     */
    private void handleFileReq(ChannelHandlerContext ctx, MsgBody msg, boolean isFile) {
        long start = System.currentTimeMillis();
        byte[] bytes = msg.getData();
        BytesReader br = new BytesReader(bytes);
        int fileNameLength = br.readInt2();
        byte[] fileNameBytes = br.read(fileNameLength);
        String fileName = new String(fileNameBytes, NettyConstants.DEFAULT_CHARSET);
        int parentDirLength = br.readInt2();
        byte[] parentDirBytes = br.read(parentDirLength);
        String parentDir = new String(parentDirBytes, NettyConstants.DEFAULT_CHARSET);
        // 文件
        String filePath = this.clientParam.getClientFilePath() + parentDir + File.separator + fileName;
        File file = new File(filePath);
        if (isFile) {
            // 父目录是否存在
            File parentFile = file.getParentFile();
            if (!parentFile.exists()) {
                boolean mk = parentFile.mkdirs();
                if (!mk) {
                    PrintUtil.log("创建文件夹出错 file : {}", parentFile.getAbsolutePath());
                }
            }
            // 文件数据
            long offset = br.readLong8();
            byte[] fileData = br.readLast();

            PrintUtil.log("写入文件 {} offset : {}, length : {}", filePath, offset, fileData.length);
            try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
                raf.seek(offset);
                raf.write(fileData);
            } catch (Exception e) {
                PrintUtil.log("文件写入出错 path : {}, size : {}", filePath, fileData.length, e);
            }
        } else {
            boolean mk = file.mkdir();
            if (!mk) {
                PrintUtil.log("创建文件夹出错 file : {}", file.getAbsolutePath());
            }
        }
        long end = System.currentTimeMillis();
        PrintUtil.log("文件写入完成 {} 耗时 {}ms", filePath, end - start);
        // 确认消息
        sendAckMsg(ctx, msg);
    }

    /**
     * 发送确认消息
     *
     * @param ctx     频道上下文
     * @param msgBody 接收到的消息
     */
    public void sendAckMsg(ChannelHandlerContext ctx, MsgBody msgBody) {
        sendMsg(ctx, msgBody.getSeq(), FileSyncCmdTypeEnum.RES_ACK, NettyConstants.EMPTY);
    }

    /**
     * 发送消息
     *
     * @param ctx  频道上下文
     * @param ack  回应 对应序号 从1开始
     * @param cmd  命令类型
     * @param data 数据
     * @see FileCmdTypeEnum
     */
    public void sendMsg(ChannelHandlerContext ctx, int ack, FileSyncCmdTypeEnum cmd, byte[] data) {
        ctx.writeAndFlush(new MsgBody(getSeq(), ack, cmd.getCmdCode(), data));
    }

    /**
     * 获取序号
     *
     * @return seq
     */
    private int getSeq() {
        return this.clientParam.getSeqCounter().incrementAndGet();
    }
}